/*
 * File    : SECertificateHandlerImpl.java
 * Created : 29-Dec-2003
 * By      : parg
 * 
 * Azureus - a Java Bittorrent client
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details ( see the LICENSE file ).
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.gudy.azureus2.core3.security.impl;

/**
 * @author parg
 *
 */

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.Authenticator;
import java.net.MalformedURLException;
import java.net.PasswordAuthentication;
import java.net.URL;
import java.security.Key;
import java.security.KeyStore;
import java.security.Permission;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

import org.gudy.azureus2.core3.config.COConfigurationManager;
import org.gudy.azureus2.core3.config.ParameterListener;
import org.gudy.azureus2.core3.logging.LogAlert;
import org.gudy.azureus2.core3.logging.LogEvent;
import org.gudy.azureus2.core3.logging.LogIDs;
import org.gudy.azureus2.core3.logging.Logger;
import org.gudy.azureus2.core3.security.SECertificateListener;
import org.gudy.azureus2.core3.security.SEKeyDetails;
import org.gudy.azureus2.core3.security.SEPasswordListener;
import org.gudy.azureus2.core3.security.SESecurityManager;
import org.gudy.azureus2.core3.util.AEMonitor;
import org.gudy.azureus2.core3.util.Constants;
import org.gudy.azureus2.core3.util.Debug;
import org.gudy.azureus2.core3.util.FileUtil;
import org.gudy.azureus2.core3.util.RandomUtils;

import com.aelitis.azureus.core.util.CopyOnWriteList;

public class SESecurityManagerImpl {
    private static final LogIDs LOGID = LogIDs.NET;

    protected static SESecurityManagerImpl singleton = new SESecurityManagerImpl();

    protected static String KEYSTORE_TYPE;

    static {
        String[] types = { "JKS", "GKR", "BKS" };

        for (int i = 0; i < types.length; i++) {
            try {
                KeyStore.getInstance(types[i]);

                KEYSTORE_TYPE = types[i];

                break;

            } catch (Throwable e) {
            }
        }

        if (KEYSTORE_TYPE == null) {

            // it'll fail later but we need to use something here

            KEYSTORE_TYPE = "JKS";
        }

        Logger.log(new LogEvent(LOGID, "Keystore type is " + KEYSTORE_TYPE));

    }

    private static boolean auto_install_certs;

    static {
        COConfigurationManager.addAndFireParameterListener("security.cert.auto.install", new ParameterListener() {
            public void parameterChanged(String parameter_name) {
                auto_install_certs = COConfigurationManager.getBooleanParameter(parameter_name);
            }
        });
    }
    protected String keystore_name;
    protected String truststore_name;

    protected List certificate_listeners = new ArrayList();
    protected CopyOnWriteList password_listeners = new CopyOnWriteList();

    private static ThreadLocal tls = new ThreadLocal() {
        public Object initialValue() {
            return (null);
        }
    };

    protected Map password_handlers = new HashMap();
    protected Map certificate_handlers = new HashMap();

    protected boolean exit_vm_permitted = false;

    private AzureusSecurityManager my_sec_man;

    protected AEMonitor this_mon = new AEMonitor("SESecurityManager");

    public static SESecurityManagerImpl getSingleton() {
        return (singleton);
    }

    private boolean initialized = false;

    private List stoppable_threads = new ArrayList();

    public void initialise() {
        synchronized (this) {
            if (initialized)
                return;
            initialized = true;
        }

        // keytool -genkey -keystore %home%\.keystore -keypass changeit -storepass changeit -keyalg rsa -alias azureus

        // keytool -export -keystore %home%\.keystore -keypass changeit -storepass changeit -alias azureus -file azureus.cer

        // keytool -import -keystore %home%\.certs -alias azureus -file azureus.cer

        // debug SSL with -Djavax.net.debug=ssl

        keystore_name = FileUtil.getUserFile(SESecurityManager.SSL_KEYS).getAbsolutePath();
        truststore_name = FileUtil.getUserFile(SESecurityManager.SSL_CERTS).getAbsolutePath();

        System.setProperty("javax.net.ssl.trustStore", truststore_name);

        System.setProperty("javax.net.ssl.trustStorePassword", SESecurityManager.SSL_PASSWORD);

        installAuthenticator();

        String[] providers =
                { "com.sun.net.ssl.internal.ssl.Provider", "org.metastatic.jessie.provider.Jessie",
                        "org.bouncycastle.jce.provider.BouncyCastleProvider" };

        String provider = null;

        for (int i = 0; i < providers.length; i++) {

            try {
                Class.forName(providers[i]).newInstance();

                provider = providers[i];

                break;

            } catch (Throwable e) {
            }
        }

        if (provider == null) {

            Debug.out("No SSL provider available");
        }

        try {
            SESecurityManagerBC.initialise();

        } catch (Throwable e) {

            Debug.out(e);

            Logger.log(new LogEvent(LOGID, LogEvent.LT_ERROR, "Bouncy Castle not available"));
        }

        installSecurityManager();

        ensureStoreExists(keystore_name);

        ensureStoreExists(truststore_name);

        initEmptyTrustStore();

        /*
         * try{ Certificate c = createSelfSignedCertificate( "Dummy", "CN=fred,OU=wap,O=wip,L=here,ST=there,C=GB", 512 ); addCertToTrustStore(
         * "SomeAlias", c); addCertToTrustStore( null, null ); }catch( Throwable e ){ Debug.printStackTrace(e); } /* try{ Certificate c =
         * createSelfSignedCertificate( "SomeAlias", "CN=fred,OU=wap,O=wip,L=here,ST=there,C=GB", 1000 ); addCertToTrustStore( "SomeAlias", c);
         * }catch( Throwable e ){ Debug.printStackTrace(e); } }
         */
    }

    private void initEmptyTrustStore() {
        try {
            File target = new File(truststore_name);

            if (target.exists() && target.length() > 2 * 1024) {

                // doesn't look very empty, bail!

                return;
            }

            KeyStore keystore = getTrustStore();

            if (keystore.size() == 0) {

                File cacerts = new File(new File(new File(System.getProperty("java.home"), "lib"), "security"), "cacerts");

                if (cacerts.exists()) {

                    FileUtil.copyFile(cacerts, target);

                    try {
                        getTrustStore();

                    } catch (Throwable e) {

                        target.delete();

                        ensureStoreExists(truststore_name);
                    }
                }
            }
        } catch (Throwable e) {
        }
    }

    public boolean resetTrustStore(boolean test_only) {
        File cacerts = new File(new File(new File(System.getProperty("java.home"), "lib"), "security"), "cacerts");

        if (!cacerts.exists()) {

            return (false);
        }

        if (test_only) {

            return (true);
        }

        File target = new File(truststore_name);

        if (target.exists()) {

            if (!target.delete()) {

                Debug.out("Failed to delete " + target);

                return (false);
            }
        }

        if (!FileUtil.copyFile(cacerts, target)) {

            Debug.out("Failed to copy file from " + cacerts + " to " + target);

            return (false);
        }

        try {
            getTrustStore();

        } catch (Throwable e) {

            Debug.out(e);

            target.delete();

            ensureStoreExists(truststore_name);

            return (false);
        }

        return (true);
    }

    public String getKeystoreName() {
        return (keystore_name);
    }

    public String getKeystorePassword() {
        return (SESecurityManager.SSL_PASSWORD);
    }

    protected void installSecurityManager() {
        if (Constants.isAndroid) {

            // can't do this

        } else {

            String prop = System.getProperty("azureus.security.manager.install", "1");

            if (prop.equals("0")) {

                Debug.outNoStack("Not installing security manager - disabled by system property");

                return;
            }

            try {
                final SecurityManager old_sec_man = System.getSecurityManager();

                my_sec_man = new AzureusSecurityManager(old_sec_man);

                System.setSecurityManager(my_sec_man);

            } catch (Throwable e) {

                Debug.printStackTrace(e);
            }
        }
    }

    public void stopThread(Thread t) {
        synchronized (stoppable_threads) {

            stoppable_threads.add(Thread.currentThread());
        }

        try {

            t.stop();

        } finally {

            synchronized (stoppable_threads) {

                stoppable_threads.remove(Thread.currentThread());
            }
        }
    }

    public void exitVM(int status) {
        try {
            exit_vm_permitted = true;

            try {
                System.exit(status);

            } catch (Throwable t) {

            }

        } finally {

            exit_vm_permitted = false;
        }
    }

    public void installAuthenticator() {
        Authenticator.setDefault(new Authenticator() {
            protected AEMonitor auth_mon = new AEMonitor("SESecurityManager:auth");

            protected PasswordAuthentication getPasswordAuthentication()

            {
                try {
                    auth_mon.enter();

                    PasswordAuthentication res =
                            getAuthentication(getRequestingPrompt(), getRequestingProtocol(), getRequestingHost(), getRequestingPort());

                    /*
                     * System.out.println( "Authenticator:getPasswordAuth: res = " + res ); if ( res != null ){ System.out.println( "    user = '" +
                     * res.getUserName() + "', pw = '" + new String(res.getPassword()) + "'" ); }
                     */

                    return (res);

                } finally {

                    auth_mon.exit();
                }
            }
        });
    }

    public PasswordAuthentication getAuthentication(String realm, String protocol, String host, int port) {
        // special case for socks auth when user is explicitly "<none>" as some servers seem to cause
        // a password prompt when no auth defined and java doesn't cache a successful blank response
        // thus causing repetitive prompts

        if (protocol.toLowerCase().startsWith("socks")) {

            String socks_user = COConfigurationManager.getStringParameter("Proxy.Username").trim();
            String socks_pw = COConfigurationManager.getStringParameter("Proxy.Password").trim();

            if (socks_user.equalsIgnoreCase("<none>")) {

                return (new PasswordAuthentication("", "".toCharArray()));
            }

            // actually getting all sorts of problems with Java not caching socks passwords
            // properly so I've abandoned prompting for them and always use the defined
            // password

            if (socks_user.length() == 0) {

                Logger.log(new LogAlert(false, LogAlert.AT_WARNING,
                        "Socks server is requesting authentication, please setup user and password in config"));
            }

            return (new PasswordAuthentication(socks_user, socks_pw.toCharArray()));
        }

        try {
            URL tracker_url = new URL(protocol + "://" + host + ":" + port + "/");

            return (getPasswordAuthentication(realm, tracker_url));

        } catch (MalformedURLException e) {

            Debug.printStackTrace(e);

            return (null);
        }
    }

    protected boolean checkKeyStoreHasEntry() {
        File f = new File(keystore_name);

        if (!f.exists()) {
            Logger.logTextResource(new LogAlert(LogAlert.UNREPEATABLE, LogAlert.AT_ERROR, "Security.keystore.empty"),
                    new String[] { keystore_name });

            return (false);
        }

        try {
            KeyStore key_store = loadKeyStore();

            Enumeration enumx = key_store.aliases();

            if (!enumx.hasMoreElements()) {
                Logger.logTextResource(new LogAlert(LogAlert.UNREPEATABLE, LogAlert.AT_ERROR, "Security.keystore.empty"),
                        new String[] { keystore_name });

                return (false);
            }

        } catch (Throwable e) {

            Logger.logTextResource(new LogAlert(LogAlert.UNREPEATABLE, LogAlert.AT_ERROR, "Security.keystore.corrupt"),
                    new String[] { keystore_name });

            return (false);
        }

        return (true);
    }

    protected boolean ensureStoreExists(String name) {
        try {
            this_mon.enter();

            KeyStore keystore = KeyStore.getInstance(KEYSTORE_TYPE);

            if (!new File(name).exists()) {

                keystore.load(null, null);

                FileOutputStream out = null;

                try {
                    out = new FileOutputStream(name);

                    keystore.store(out, SESecurityManager.SSL_PASSWORD.toCharArray());

                } finally {

                    if (out != null) {

                        out.close();
                    }
                }

                return (true);

            } else {

                return (false);
            }
        } catch (Throwable e) {

            Debug.printStackTrace(e);

            return (false);

        } finally {

            this_mon.exit();
        }
    }

    public KeyStore getKeyStore()

    throws Exception {
        return (loadKeyStore());
    }

    public KeyStore getTrustStore()

    throws Exception {
        KeyStore keystore = KeyStore.getInstance(KEYSTORE_TYPE);

        if (!new File(truststore_name).exists()) {

            keystore.load(null, null);

        } else {

            FileInputStream in = null;

            try {
                in = new FileInputStream(truststore_name);

                keystore.load(in, SESecurityManager.SSL_PASSWORD.toCharArray());

            } finally {

                if (in != null) {

                    in.close();
                }
            }
        }

        return (keystore);
    }

    protected KeyStore loadKeyStore()

    throws Exception {
        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");

        return (loadKeyStore(keyManagerFactory));
    }

    protected KeyStore loadKeyStore(KeyManagerFactory keyManagerFactory)

    throws Exception {
        KeyStore key_store = KeyStore.getInstance(KEYSTORE_TYPE);

        if (!new File(keystore_name).exists()) {

            key_store.load(null, null);

        } else {

            InputStream kis = null;

            try {
                kis = new FileInputStream(keystore_name);

                key_store.load(kis, SESecurityManager.SSL_PASSWORD.toCharArray());

            } finally {

                if (kis != null) {

                    kis.close();
                }
            }
        }

        keyManagerFactory.init(key_store, SESecurityManager.SSL_PASSWORD.toCharArray());

        return (key_store);
    }

    public SSLServerSocketFactory getSSLServerSocketFactory()

    throws Exception {
        if (!checkKeyStoreHasEntry()) {

            return (null);
        }

        SSLContext context = SSLContext.getInstance("SSL");

        // Create the key manager factory used to extract the server key

        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");

        loadKeyStore(keyManagerFactory);

        // Initialize the context with the key managers

        context.init(keyManagerFactory.getKeyManagers(), null, RandomUtils.SECURE_RANDOM);

        SSLServerSocketFactory factory = context.getServerSocketFactory();

        return (factory);
    }

    public SEKeyDetails getKeyDetails(String alias)

    throws Exception {
        // Create the key manager factory used to extract the server key

        KeyStore key_store = loadKeyStore();

        final Key key = key_store.getKey(alias, SESecurityManager.SSL_PASSWORD.toCharArray());

        if (key == null) {

            return (null);
        }

        java.security.cert.Certificate[] chain = key_store.getCertificateChain(alias);

        final X509Certificate[] res = new X509Certificate[chain.length];

        for (int i = 0; i < chain.length; i++) {

            if (!(chain[i] instanceof X509Certificate)) {

                throw (new Exception("Certificate chain must be comprised of X509Certificate entries"));
            }

            res[i] = (X509Certificate) chain[i];
        }

        return (new SEKeyDetails() {
            public Key getKey() {
                return (key);
            }

            public X509Certificate[] getCertificateChain() {
                return (res);
            }
        });
    }

    public Certificate createSelfSignedCertificate(String alias, String cert_dn, int strength)

    throws Exception {
        return (SESecurityManagerBC.createSelfSignedCertificate(this, alias, cert_dn, strength));
    }

    public TrustManagerFactory getTrustManagerFactory() {
        try {
            this_mon.enter();

            KeyStore keystore = getTrustStore();

            TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());

            tmf.init(keystore);

            return (tmf);

        } catch (Throwable e) {

            Debug.out(e);

            return (null);

        } finally {

            this_mon.exit();
        }
    }

    public SSLSocketFactory getSSLSocketFactory() {
        try {
            this_mon.enter();

            KeyStore keystore = getTrustStore();

            TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());

            tmf.init(keystore);

            SSLContext ctx = SSLContext.getInstance("SSL");

            ctx.init(null, tmf.getTrustManagers(), null);

            SSLSocketFactory factory = ctx.getSocketFactory();

            return (factory);

        } catch (Throwable e) {

            Debug.printStackTrace(e);

            return ((SSLSocketFactory) SSLSocketFactory.getDefault());

        } finally {

            this_mon.exit();
        }
    }

    public SSLSocketFactory installServerCertificates(URL https_url) {
        try {
            this_mon.enter();

            String host = https_url.getHost();
            int port = https_url.getPort();

            if (port == -1) {
                port = 443;
            }

            SSLSocket socket = null;

            try {

                // to get the server certs we have to use an "all trusting" trust manager

                TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
                    public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                        return null;
                    }

                    public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {
                    }

                    public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {
                    }
                } };

                SSLContext sc = SSLContext.getInstance("SSL");

                sc.init(null, trustAllCerts, RandomUtils.SECURE_RANDOM);

                SSLSocketFactory factory = sc.getSocketFactory();

                socket = (SSLSocket) factory.createSocket(host, port);

                socket.startHandshake();

                java.security.cert.Certificate[] serverCerts = socket.getSession().getPeerCertificates();

                if (serverCerts.length == 0) {

                    return (null);
                }

                java.security.cert.Certificate cert = serverCerts[0];

                java.security.cert.X509Certificate x509_cert;

                if (cert instanceof java.security.cert.X509Certificate) {

                    x509_cert = (java.security.cert.X509Certificate) cert;

                } else {

                    java.security.cert.CertificateFactory cf = java.security.cert.CertificateFactory.getInstance("X.509");

                    x509_cert = (java.security.cert.X509Certificate) cf.generateCertificate(new ByteArrayInputStream(cert.getEncoded()));
                }

                String resource = https_url.toString();

                int param_pos = resource.indexOf("?");

                if (param_pos != -1) {

                    resource = resource.substring(0, param_pos);
                }

                // recalc - don't use port above as it may have been changed

                String url_s = https_url.getProtocol() + "://" + https_url.getHost() + ":" + https_url.getPort() + "/";

                Object[] handler = (Object[]) certificate_handlers.get(url_s);

                String alias = host.concat(":").concat(String.valueOf(port));

                KeyStore keystore = getTrustStore();

                byte[] new_encoded = x509_cert.getEncoded();

                int count = 0;

                while (count < 256) {

                    String test_alias = count == 0 ? alias : (alias + "." + count);

                    Certificate existing = keystore.getCertificate(test_alias);

                    if (existing != null) {

                        if (Arrays.equals(new_encoded, existing.getEncoded())) {

                            alias = test_alias;

                            break;
                        }
                    } else {

                        alias = test_alias;

                        break;
                    }

                    count++;
                }

                if (auto_install_certs) {

                    return (addCertToTrustStore(alias, cert, true));

                } else {

                    if (handler != null) {

                        if (((SECertificateListener) handler[0]).trustCertificate(resource, x509_cert)) {

                            return (addCertToTrustStore(alias, cert, true));
                        }
                    }

                    for (int i = 0; i < certificate_listeners.size(); i++) {

                        if (((SECertificateListener) certificate_listeners.get(i)).trustCertificate(resource, x509_cert)) {

                            return (addCertToTrustStore(alias, cert, true));
                        }
                    }
                }

                return (null);

            } catch (Throwable e) {

                Debug.printStackTrace(e);

                return (null);

            } finally {

                if (socket != null) {

                    try {
                        socket.close();

                    } catch (Throwable e) {

                        Debug.printStackTrace(e);
                    }
                }
            }
        } finally {

            this_mon.exit();
        }
    }

    public SSLSocketFactory installServerCertificates(String alias, String host, int port) {
        try {
            this_mon.enter();

            SSLSocket socket = null;

            try {

                // to get the server certs we have to use an "all trusting" trust manager

                TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
                    public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                        return null;
                    }

                    public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {
                    }

                    public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {
                    }
                } };

                SSLContext sc = SSLContext.getInstance("SSL");

                sc.init(null, trustAllCerts, RandomUtils.SECURE_RANDOM);

                SSLSocketFactory factory = sc.getSocketFactory();

                socket = (SSLSocket) factory.createSocket(host, port);

                socket.startHandshake();

                java.security.cert.Certificate[] serverCerts = socket.getSession().getPeerCertificates();

                if (serverCerts.length == 0) {

                    return (null);
                }

                java.security.cert.Certificate cert = serverCerts[0];

                java.security.cert.X509Certificate x509_cert;

                if (cert instanceof java.security.cert.X509Certificate) {

                    x509_cert = (java.security.cert.X509Certificate) cert;

                } else {

                    java.security.cert.CertificateFactory cf = java.security.cert.CertificateFactory.getInstance("X.509");

                    x509_cert = (java.security.cert.X509Certificate) cf.generateCertificate(new ByteArrayInputStream(cert.getEncoded()));
                }

                return (addCertToTrustStore(alias, x509_cert, false));

            } catch (Throwable e) {

                Debug.printStackTrace(e);

                return (null);

            } finally {

                if (socket != null) {

                    try {
                        socket.close();

                    } catch (Throwable e) {

                        Debug.printStackTrace(e);
                    }
                }
            }
        } finally {

            this_mon.exit();
        }
    }

    protected void addCertToKeyStore(String alias, Key public_key, java.security.cert.Certificate[] certChain)

    throws Exception {
        try {
            this_mon.enter();

            KeyStore key_store = loadKeyStore();

            if (key_store.containsAlias(alias)) {

                key_store.deleteEntry(alias);
            }

            key_store.setKeyEntry(alias, public_key, SESecurityManager.SSL_PASSWORD.toCharArray(), certChain);

            FileOutputStream out = null;

            try {
                out = new FileOutputStream(keystore_name);

                key_store.store(out, SESecurityManager.SSL_PASSWORD.toCharArray());

            } catch (Throwable e) {

                Debug.printStackTrace(e);

            } finally {

                if (out != null) {

                    out.close();
                }
            }
        } finally {

            this_mon.exit();
        }
    }

    protected SSLSocketFactory addCertToTrustStore(String alias, java.security.cert.Certificate cert, boolean update_https_factory)

    throws Exception {
        try {
            this_mon.enter();

            KeyStore keystore = getTrustStore();

            if (cert != null) {

                if (keystore.containsAlias(alias)) {

                    keystore.deleteEntry(alias);
                }

                keystore.setCertificateEntry(alias, cert);

                FileOutputStream out = null;

                try {
                    out = new FileOutputStream(truststore_name);

                    keystore.store(out, SESecurityManager.SSL_PASSWORD.toCharArray());

                } finally {

                    if (out != null) {

                        out.close();
                    }
                }
            }

            // pick up the changed trust store

            TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());

            tmf.init(keystore);

            SSLContext ctx = SSLContext.getInstance("SSL");

            ctx.init(null, tmf.getTrustManagers(), null);

            SSLSocketFactory factory = ctx.getSocketFactory();

            if (update_https_factory) {

                HttpsURLConnection.setDefaultSSLSocketFactory(factory);
            }

            return (factory);
        } finally {

            this_mon.exit();
        }
    }

    public PasswordAuthentication getPasswordAuthentication(String realm, URL tracker) {
        SEPasswordListener thread_listener = (SEPasswordListener) tls.get();

        if (thread_listener != null) {

            return (thread_listener.getAuthentication(realm, tracker));
        }

        Object[] handler = (Object[]) password_handlers.get(tracker.toString());

        if (handler != null) {

            try {
                return (((SEPasswordListener) handler[0]).getAuthentication(realm, (URL) handler[1]));

            } catch (Throwable e) {

                Debug.printStackTrace(e);
            }
        }

        Iterator it = password_listeners.iterator();

        while (it.hasNext()) {

            try {
                PasswordAuthentication res = ((SEPasswordListener) it.next()).getAuthentication(realm, tracker);

                if (res != null) {

                    return (res);
                }
            } catch (Throwable e) {

                Debug.printStackTrace(e);
            }
        }

        return (null);
    }

    public void setPasswordAuthenticationOutcome(String realm, URL tracker, boolean success) {
        SEPasswordListener thread_listener = (SEPasswordListener) tls.get();

        if (thread_listener != null) {

            thread_listener.setAuthenticationOutcome(realm, tracker, success);
        }

        Iterator it = password_listeners.iterator();

        while (it.hasNext()) {

            ((SEPasswordListener) it.next()).setAuthenticationOutcome(realm, tracker, success);
        }
    }

    public void addPasswordListener(SEPasswordListener l) {
        try {
            this_mon.enter();

            password_listeners.add(l);

        } finally {

            this_mon.exit();
        }
    }

    public void removePasswordListener(SEPasswordListener l) {
        try {
            this_mon.enter();

            password_listeners.remove(l);

        } finally {

            this_mon.exit();
        }
    }

    public void clearPasswords() {
        SEPasswordListener thread_listener = (SEPasswordListener) tls.get();

        if (thread_listener != null) {

            thread_listener.clearPasswords();
        }

        Iterator it = password_listeners.iterator();

        while (it.hasNext()) {

            try {
                ((SEPasswordListener) it.next()).clearPasswords();

            } catch (Throwable e) {

                Debug.printStackTrace(e);
            }
        }
    }

    public void setThreadPasswordHandler(SEPasswordListener l) {
        tls.set(l);
    }

    public void unsetThreadPasswordHandler() {
        tls.set(null);
    }

    public void setPasswordHandler(URL url, SEPasswordListener l) {
        String url_s = url.getProtocol() + "://" + url.getHost() + ":" + url.getPort() + "/";

        if (l == null) {

            password_handlers.remove(url_s);

        } else {

            password_handlers.put(url_s, new Object[] { l, url });
        }
    }

    public void addCertificateListener(SECertificateListener l) {
        try {
            this_mon.enter();

            certificate_listeners.add(l);

        } finally {

            this_mon.exit();
        }
    }

    public void setCertificateHandler(URL url, SECertificateListener l) {
        String url_s = url.getProtocol() + "://" + url.getHost() + ":" + url.getPort() + "/";

        if (l == null) {

            certificate_handlers.remove(url_s);

        } else {

            certificate_handlers.put(url_s, new Object[] { l, url });
        }
    }

    public void removeCertificateListener(SECertificateListener l) {
        try {
            this_mon.enter();

            certificate_listeners.remove(l);

        } finally {

            this_mon.exit();
        }
    }

    public Class[] getClassContext() {
        if (my_sec_man == null) {

            return (new Class[0]);
        }

        return (my_sec_man.getClassContext());
    }

    private final class AzureusSecurityManager extends SecurityManager {
        private SecurityManager old_sec_man;

        private AzureusSecurityManager(SecurityManager _old_sec_man) {
            old_sec_man = _old_sec_man;
        }

        public void checkAccept(String host, int port) {
            // do nothing
        }

        public void checkRead(String file) {
            // do nothing
        }

        public void checkWrite(String file) {
            // do nothing
        }

        public void checkConnect(String host, int port) {
        }

        public void checkExit(int status) {
            if (old_sec_man != null) {

                old_sec_man.checkExit(status);
            }

            if (!exit_vm_permitted) {

                String prop = System.getProperty("azureus.security.manager.permitexit", "0");

                if (prop.equals("0")) {

                    throw (new SecurityException("VM exit operation prohibited"));
                }
            }
        }

        public void checkPermission(Permission perm) {
            checkPermission(perm, null);
        }

        public void checkPermission(Permission perm, Object context) {
            if (perm instanceof RuntimePermission) {

                String name = perm.getName();

                if (name.equals("stopThread")) {

                    synchronized (stoppable_threads) {

                        if (stoppable_threads.contains(Thread.currentThread())) {

                            return;
                        }
                    }

                    throw (new SecurityException("Thread.stop operation prohibited"));

                } else if (name.equals("setSecurityManager")) {

                    throw (new SecurityException("Permission Denied"));
                }
            }

            if (old_sec_man != null) {

                if (context == null) {

                    old_sec_man.checkPermission(perm);

                } else {

                    old_sec_man.checkPermission(perm, context);
                }
            }
        }

        public Class[] getClassContext() {
            Class[] res = super.getClassContext();

            if (res.length <= 3) {

                return (new Class[0]);
            }

            Class[] trimmed = new Class[res.length - 3];

            System.arraycopy(res, 3, trimmed, 0, trimmed.length);

            return (trimmed);
        }

    };

    public static void main(String[] args) {
        SESecurityManagerImpl man = SESecurityManagerImpl.getSingleton();

        man.initialise();

        try {
            man.createSelfSignedCertificate("SomeAlias", "CN=fred,OU=wap,O=wip,L=here,ST=there,C=GB", 1000);

        } catch (Throwable e) {

            Debug.printStackTrace(e);
        }
    }
}
